/*
 * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package javax.print;

import java.util.ArrayList;
import java.util.Iterator;
import javax.print.attribute.AttributeSet;

import sun.awt.AppContext;
import java.util.ServiceLoader;
import java.util.ServiceConfigurationError;

/**
 * Implementations of this class provide lookup services for
 * print services (typically equivalent to printers) of a particular type.
 * <p>
 * Multiple implementations may be installed concurrently.
 * All implementations must be able to describe the located printers
 * as instances of a PrintService.
 * Typically implementations of this service class are located
 * automatically in JAR files (see the SPI JAR file specification).
 * These classes must be instantiable using a default constructor.
 * Alternatively applications may explicitly register instances
 * at runtime.
 * <p>
 * Applications use only the static methods of this abstract class.
 * The instance methods are implemented by a service provider in a subclass
 * and the unification of the results from all installed lookup classes
 * are reported by the static methods of this class when called by
 * the application.
 * <p>
 * A PrintServiceLookup implementor is recommended to check for the
 * SecurityManager.checkPrintJobAccess() to deny access to untrusted code.
 * Following this recommended policy means that untrusted code may not
 * be able to locate any print services. Downloaded applets are the most
 * common example of untrusted code.
 * <p>
 * This check is made on a per lookup service basis to allow flexibility in
 * the policy to reflect the needs of different lookup services.
 * <p>
 * Services which are registered by registerService(PrintService)
 * will not be included in lookup results if a security manager is
 * installed and its checkPrintJobAccess() method denies access.
 */

public abstract class PrintServiceLookup {

  static class Services {

    private ArrayList listOfLookupServices = null;
    private ArrayList registeredServices = null;
  }

  private static Services getServicesForContext() {
    Services services =
        (Services) AppContext.getAppContext().get(Services.class);
    if (services == null) {
      services = new Services();
      AppContext.getAppContext().put(Services.class, services);
    }
    return services;
  }

  private static ArrayList getListOfLookupServices() {
    return getServicesForContext().listOfLookupServices;
  }

  private static ArrayList initListOfLookupServices() {
    ArrayList listOfLookupServices = new ArrayList();
    getServicesForContext().listOfLookupServices = listOfLookupServices;
    return listOfLookupServices;
  }


  private static ArrayList getRegisteredServices() {
    return getServicesForContext().registeredServices;
  }

  private static ArrayList initRegisteredServices() {
    ArrayList registeredServices = new ArrayList();
    getServicesForContext().registeredServices = registeredServices;
    return registeredServices;
  }

  /**
   * Locates print services capable of printing the specified
   * {@link DocFlavor}.
   *
   * @param flavor the flavor to print. If null, this constraint is not used.
   * @param attributes attributes that the print service must support. If null this constraint is
   * not used.
   * @return array of matching <code>PrintService</code> objects representing print services that
   * support the specified flavor attributes.  If no services match, the array is zero-length.
   */
  public static final PrintService[]
  lookupPrintServices(DocFlavor flavor,
      AttributeSet attributes) {
    ArrayList list = getServices(flavor, attributes);
    return (PrintService[]) (list.toArray(new PrintService[list.size()]));
  }


  /**
   * Locates MultiDoc print Services capable of printing MultiDocs
   * containing all the specified doc flavors.
   * <P> This method is useful to help locate a service that can print
   * a <code>MultiDoc</code> in which the elements may be different
   * flavors. An application could perform this itself by multiple lookups
   * on each <code>DocFlavor</code> in turn and collating the results,
   * but the lookup service may be able to do this more efficiently.
   *
   * @param flavors the flavors to print. If null or empty this constraint is not used. Otherwise
   * return only multidoc print services that can print all specified doc flavors.
   * @param attributes attributes that the print service must support.  If null this constraint is
   * not used.
   * @return array of matching {@link MultiDocPrintService} objects. If no services match, the array
   * is zero-length.
   */
  public static final MultiDocPrintService[]
  lookupMultiDocPrintServices(DocFlavor[] flavors,
      AttributeSet attributes) {
    ArrayList list = getMultiDocServices(flavors, attributes);
    return (MultiDocPrintService[])
        list.toArray(new MultiDocPrintService[list.size()]);
  }


  /**
   * Locates the default print service for this environment.
   * This may return null.
   * If multiple lookup services each specify a default, the
   * chosen service is not precisely defined, but a
   * platform native service, rather than an installed service,
   * is usually returned as the default.  If there is no clearly
   * identifiable
   * platform native default print service, the default is the first
   * to be located in an implementation-dependent manner.
   * <p>
   * This may include making use of any preferences API that is available
   * as part of the Java or native platform.
   * This algorithm may be overridden by a user setting the property
   * javax.print.defaultPrinter.
   * A service specified must be discovered to be valid and currently
   * available to be returned as the default.
   *
   * @return the default PrintService.
   */

  public static final PrintService lookupDefaultPrintService() {

    Iterator psIterator = getAllLookupServices().iterator();
    while (psIterator.hasNext()) {
      try {
        PrintServiceLookup lus = (PrintServiceLookup) psIterator.next();
        PrintService service = lus.getDefaultPrintService();
        if (service != null) {
          return service;
        }
      } catch (Exception e) {
      }
    }
    return null;
  }


  /**
   * Allows an application to explicitly register a class that
   * implements lookup services. The registration will not persist
   * across VM invocations.
   * This is useful if an application needs to make a new service
   * available that is not part of the installation.
   * If the lookup service is already registered, or cannot be registered,
   * the method returns false.
   * <p>
   *
   * @param sp an implementation of a lookup service.
   * @return <code>true</code> if the new lookup service is newly registered; <code>false</code>
   * otherwise.
   */
  public static boolean registerServiceProvider(PrintServiceLookup sp) {
    synchronized (PrintServiceLookup.class) {
      Iterator psIterator = getAllLookupServices().iterator();
      while (psIterator.hasNext()) {
        try {
          Object lus = psIterator.next();
          if (lus.getClass() == sp.getClass()) {
            return false;
          }
        } catch (Exception e) {
        }
      }
      getListOfLookupServices().add(sp);
      return true;
    }

  }


  /**
   * Allows an application to directly register an instance of a
   * class which implements a print service.
   * The lookup operations for this service will be
   * performed by the PrintServiceLookup class using the attribute
   * values and classes reported by the service.
   * This may be less efficient than a lookup
   * service tuned for that service.
   * Therefore registering a <code>PrintServiceLookup</code> instance
   * instead is recommended.
   * The method returns true if this service is not previously
   * registered and is now successfully registered.
   * This method should not be called with StreamPrintService instances.
   * They will always fail to register and the method will return false.
   *
   * @param service an implementation of a print service.
   * @return <code>true</code> if the service is newly registered; <code>false</code> otherwise.
   */

  public static boolean registerService(PrintService service) {
    synchronized (PrintServiceLookup.class) {
      if (service instanceof StreamPrintService) {
        return false;
      }
      ArrayList registeredServices = getRegisteredServices();
      if (registeredServices == null) {
        registeredServices = initRegisteredServices();
      } else {
        if (registeredServices.contains(service)) {
          return false;
        }
      }
      registeredServices.add(service);
      return true;
    }
  }


  /**
   * Locates services that can be positively confirmed to support
   * the combination of attributes and DocFlavors specified.
   * This method is not called directly by applications.
   * <p>
   * Implemented by a service provider, used by the static methods
   * of this class.
   * <p>
   * The results should be the same as obtaining all the PrintServices
   * and querying each one individually on its support for the
   * specified attributes and flavors, but the process can be more
   * efficient by taking advantage of the capabilities of lookup services
   * for the print services.
   *
   * @param flavor of document required.  If null it is ignored.
   * @param attributes required to be supported. If null this constraint is not used.
   * @return array of matching PrintServices. If no services match, the array is zero-length.
   */
  public abstract PrintService[] getPrintServices(DocFlavor flavor,
      AttributeSet attributes);

  /**
   * Not called directly by applications.
   * Implemented by a service provider, used by the static methods
   * of this class.
   *
   * @return array of all PrintServices known to this lookup service class. If none are found, the
   * array is zero-length.
   */
  public abstract PrintService[] getPrintServices();


  /**
   * Not called directly by applications.
   * <p>
   * Implemented by a service provider, used by the static methods
   * of this class.
   * <p>
   * Locates MultiDoc print services which can be positively confirmed
   * to support the combination of attributes and DocFlavors specified.
   * <p>
   *
   * @param flavors of documents required. If null or empty it is ignored.
   * @param attributes required to be supported. If null this constraint is not used.
   * @return array of matching PrintServices. If no services match, the array is zero-length.
   */
  public abstract MultiDocPrintService[]
  getMultiDocPrintServices(DocFlavor[] flavors,
      AttributeSet attributes);

  /**
   * Not called directly by applications.
   * Implemented by a service provider, and called by the print lookup
   * service
   *
   * @return the default PrintService for this lookup service. If there is no default, returns null.
   */
  public abstract PrintService getDefaultPrintService();

  private static ArrayList getAllLookupServices() {
    synchronized (PrintServiceLookup.class) {
      ArrayList listOfLookupServices = getListOfLookupServices();
      if (listOfLookupServices != null) {
        return listOfLookupServices;
      } else {
        listOfLookupServices = initListOfLookupServices();
      }
      try {
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedExceptionAction() {
              public Object run() {
                Iterator<PrintServiceLookup> iterator =
                    ServiceLoader.load(PrintServiceLookup.class).
                        iterator();
                ArrayList los = getListOfLookupServices();
                while (iterator.hasNext()) {
                  try {
                    los.add(iterator.next());
                  } catch (ServiceConfigurationError err) {
                                    /* In the applet case, we continue */
                    if (System.getSecurityManager() != null) {
                      err.printStackTrace();
                    } else {
                      throw err;
                    }
                  }
                }
                return null;
              }
            });
      } catch (java.security.PrivilegedActionException e) {
      }

      return listOfLookupServices;
    }
  }

  private static ArrayList getServices(DocFlavor flavor,
      AttributeSet attributes) {

    ArrayList listOfServices = new ArrayList();
    Iterator psIterator = getAllLookupServices().iterator();
    while (psIterator.hasNext()) {
      try {
        PrintServiceLookup lus = (PrintServiceLookup) psIterator.next();
        PrintService[] services = null;
        if (flavor == null && attributes == null) {
          try {
            services = lus.getPrintServices();
          } catch (Throwable tr) {
          }
        } else {
          services = lus.getPrintServices(flavor, attributes);
        }
        if (services == null) {
          continue;
        }
        for (int i = 0; i < services.length; i++) {
          listOfServices.add(services[i]);
        }
      } catch (Exception e) {
      }
    }
        /* add any directly registered services */
    ArrayList registeredServices = null;
    try {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
        security.checkPrintJobAccess();
      }
      registeredServices = getRegisteredServices();
    } catch (SecurityException se) {
    }
    if (registeredServices != null) {
      PrintService[] services = (PrintService[])
          registeredServices.toArray(
              new PrintService[registeredServices.size()]);
      for (int i = 0; i < services.length; i++) {
        if (!listOfServices.contains(services[i])) {
          if (flavor == null && attributes == null) {
            listOfServices.add(services[i]);
          } else if (((flavor != null &&
              services[i].isDocFlavorSupported(flavor)) ||
              flavor == null) &&
              null == services[i].getUnsupportedAttributes(
                  flavor, attributes)) {
            listOfServices.add(services[i]);
          }
        }
      }
    }
    return listOfServices;
  }

  private static ArrayList getMultiDocServices(DocFlavor[] flavors,
      AttributeSet attributes) {

    ArrayList listOfServices = new ArrayList();
    Iterator psIterator = getAllLookupServices().iterator();
    while (psIterator.hasNext()) {
      try {
        PrintServiceLookup lus = (PrintServiceLookup) psIterator.next();
        MultiDocPrintService[] services =
            lus.getMultiDocPrintServices(flavors, attributes);
        if (services == null) {
          continue;
        }
        for (int i = 0; i < services.length; i++) {
          listOfServices.add(services[i]);
        }
      } catch (Exception e) {
      }
    }
        /* add any directly registered services */
    ArrayList registeredServices = null;
    try {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
        security.checkPrintJobAccess();
      }
      registeredServices = getRegisteredServices();
    } catch (Exception e) {
    }
    if (registeredServices != null) {
      PrintService[] services = (PrintService[])
          registeredServices.toArray(
              new PrintService[registeredServices.size()]);
      for (int i = 0; i < services.length; i++) {
        if (services[i] instanceof MultiDocPrintService &&
            !listOfServices.contains(services[i])) {
          if (flavors == null || flavors.length == 0) {
            listOfServices.add(services[i]);
          } else {
            boolean supported = true;
            for (int f = 0; f < flavors.length; f++) {
              if (services[i].isDocFlavorSupported(flavors[f])) {

                if (services[i].getUnsupportedAttributes(
                    flavors[f], attributes) != null) {
                  supported = false;
                  break;
                }
              } else {
                supported = false;
                break;
              }
            }
            if (supported) {
              listOfServices.add(services[i]);
            }
          }
        }
      }
    }
    return listOfServices;
  }

}
